package com.sunxboy.platform.log4j;

import java.util.Map;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.InputStreamSource;
import org.springframework.core.io.Resource;

import com.sunxboy.platform.option.CommandLineOptions;

/**
 * This class when dropped in spring context will configure log4j before beans are initialized (except the ones that
 * this processor depends on)
 */
public class Log4jSpringContextConfigurator implements BeanFactoryPostProcessor {
    private final Log4jConfigurator log4jConfigurator;
    private InputStreamSource log4jXmlConfigInputStreamSource;
    private static DefaultResourceLoader resourceLoader = new DefaultResourceLoader();

    /**
     * Construct a new configurator. Log4J will be configured from a valid Freemarker template file found at one of the
     * following locations (in order):
     * <ul>
     * <li>the (Spring style) resource location specified in the --core:logConfig startup argument (if specified)</li>
     * <li>com/sunxboy/common/log4j.xml located in the classpath</li>
     * </ul>
     * If none of the above are available, an exception will be thrown on startup.
     */
    public Log4jSpringContextConfigurator() {
        this(new Log4jConfigurator());
    }

    /**
     * As per {@link #Log4jSpringContextConfigurator()}, but using the passed in configurator instead of the default.
     * 
     * @param configurator A configurator for Log4j
     */
    public Log4jSpringContextConfigurator(Log4jConfigurator configurator) {
        this(configurator, (InputStreamSource) null);
    }

    /**
     * Configure Log4j with the specific log config file (a FreeMarker template).
     * 
     * @param log4jConfigurator
     * @param loggingConfigResource The resource path (using Spring's loader mechanism, so can be prefixed with
     *            'classpath:' or 'file:' etc
     */
    public Log4jSpringContextConfigurator(Log4jConfigurator log4jConfigurator, String loggingConfigResource) {
        this(log4jConfigurator, resourceLoader.getResource(loggingConfigResource));
    }

    public Log4jSpringContextConfigurator(Log4jConfigurator log4jConfigurator, final InputStreamSource log4jXmlConfigInputStreamSource) {
        this.log4jConfigurator = log4jConfigurator;
        this.log4jXmlConfigInputStreamSource = log4jXmlConfigInputStreamSource;
    }

    public void setExtraData(Map<String, Object> extraData) {
        log4jConfigurator.setData(extraData);
    }

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        if (log4jXmlConfigInputStreamSource == null) {
            findSource(beanFactory);
        }
        this.log4jConfigurator.configureFromTemplate(this.log4jXmlConfigInputStreamSource);
    }

    private void findSource(ConfigurableListableBeanFactory beanFactory) {
        CommandLineOptions options = null;
        try {
            options = (CommandLineOptions) beanFactory.getBean("core.CommandLineOptions");
        }
        catch (BeansException e) {
            throw new RuntimeException("No log4j config file specified, and cannot find command line options bean in context", e);
        }
        String configLocation = options.getLogConfigLocation();
        Resource res = resourceLoader.getResource(configLocation);
        if (!res.exists()) {
            throw new RuntimeException("Log4j config file [" + configLocation + "] not found, please specify an existing file " +
                    "using the --core:logConfig command line argument");
        }
        this.log4jXmlConfigInputStreamSource = res;
    }
}
